import json
import math
from typing import List, Dict, Tuple

# 相关参数初始化
with open('config.json', 'r', encoding='utf-8') as file:
    config = json.load(file)
    print(f'config.signal: {config["areaCheckDistance"]}')
    portCheckDistance = config['areaCheckDistance']['portDistance']
    impAreaCheckDistance = config['areaCheckDistance']['impAreaDistance']
EARTH_R = 6371000  # 地球平均半径，米

def haversine_m(lon1: float, lat1: float,
                lon2: float, lat2: float) -> float:
    """两点间大圆距离（米）"""
    dlat = math.radians(lat2 - lat1)
    dlon = math.radians(lon2 - lon1)

    lat1_rad = math.radians(lat1)
    lat2_rad = math.radians(lat2)

    a = (math.sin(dlat / 2) ** 2 +
         math.cos(lat1_rad) * math.cos(lat2_rad) *
         math.sin(dlon / 2) ** 2)
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    return EARTH_R * c

def extend_point(lon1: float, lat1: float,
                 lon2: float, lat2: float,
                 extend_m: float) -> Tuple[float, float]:
    """沿 1->2 方向延长 extend_m 米后的 (lon, lat)"""
    dist = haversine_m(lon1, lat1, lon2, lat2)
    if dist < 1e-3:
        return lon2, lat2
    k = extend_m / dist
    lon = lon2 + k * (lon2 - lon1)
    lat = lat2 + k * (lat2 - lat1) * math.cos(math.radians(lat1)) / math.cos(math.radians(lat2))
    return lon, lat

# ------------- 矩形区域工具 ------------- #
class Rect:
    def __init__(self, area_pos: List[float]):
        lon1, lat1, lon2, lat2 = area_pos
        self.lon_min = min(lon1, lon2)
        self.lon_max = max(lon1, lon2)
        self.lat_min = min(lat1, lat2)
        self.lat_max = max(lat1, lat2)

    def contains(self, lon: float, lat: float) -> bool:
        return self.lon_min <= lon <= self.lon_max and self.lat_min <= lat <= self.lat_max

# ------------- 线段与矩形相交 ------------- #
def intersect_rect(lon1: float, lat1: float,
                   lon2: float, lat2: float,
                   rect: Rect) -> bool:
    """线段与轴对齐矩形相交（快速近似：矩形四边做叉积）"""
    # 1. 任一点在矩形内即相交
    if rect.contains(lon1, lat1) or rect.contains(lon2, lat2):
        return True

    # 2. 与四条边做跨立实验
    def ccw(A, B, C):
        return (C[1]-A[1])*(B[0]-A[0]) - (B[1]-A[1])*(C[0]-A[0])

    A = (lon1, lat1)
    B = (lon2, lat2)
    edges = [
        ((rect.lon_min, rect.lat_min), (rect.lon_max, rect.lat_min)),
        ((rect.lon_max, rect.lat_min), (rect.lon_max, rect.lat_max)),
        ((rect.lon_max, rect.lat_max), (rect.lon_min, rect.lat_max)),
        ((rect.lon_min, rect.lat_max), (rect.lon_min, rect.lat_min))
    ]
    for (C, D) in edges:
        if (ccw(A, C, D) * ccw(B, C, D) < 0) and (ccw(C, A, B) * ccw(D, A, B) < 0):
            return True
    return False

# ------------- 主处理逻辑 ------------- #
def process_track(track: Dict,
                  rect,
                  areaId: str,
                  area_type: str) -> List[Dict]:
    """
    根据区域类型 area_type 选择延长距离和事件代码映射
    area_type = 'port'   -> extend_m = portCheckDistance，事件代码 2B/3M/3N
    area_type = 'impArea'-> extend_m = impAreaCheckDistance，事件代码 3R/3P/3Q
    """
    # 1. 根据 area_type 设置 extend_m 与代码映射
    if area_type == 'port':
        extend_m = portCheckDistance
        code_map = {
            'enterArea': '2B',
            'approaching': '3M',
            'leaving': '3N'
        }
    elif area_type == 'impArea':
        extend_m = impAreaCheckDistance
        code_map = {
            'enterArea': '3R',
            'approaching': '3P',
            'leaving': '3Q'
        }
    else:
        raise ValueError(f"Unsupported area_type: {area_type}")

    # 2. 算法主体
    pts = track['trackData']
    if not pts:
        return []
    pts.sort(key=lambda p: p['timestamp'])

    res = []
    n = len(pts)
    i = 0
    while i < n:
        # 1. 连续“进入”段
        j = i
        while j < n and rect.contains(pts[j]['longitude'], pts[j]['latitude']):
            j += 1
        if j > i:
            res.append({
                "trackId": track['trackId'],
                "areaId": areaId,
                "actionKey": code_map['enterArea'],
                "startTime": pts[i]['timestamp'],
                "endTime": pts[j - 1]['timestamp'],
                'properties': f'{pts[i]["longitude"]};{pts[i]["latitude"]}'
            })
            i = j
            continue

        # 2. 驶向/驶离判断（至少一点在区域外）
        if i + 1 >= n:
            break

        prev = pts[i]
        curr = pts[i + 1]

        # 速度过滤
        if prev.get('speed', 0) < 1 or curr.get('speed', 0) < 1:
            i += 1
            continue

        # 跳过当前点在区域内的点
        if rect.contains(curr['longitude'], curr['latitude']):
            i += 1
            continue

        lon1, lat1 = prev['longitude'], prev['latitude']
        lon2, lat2 = curr['longitude'], curr['latitude']

        # 2a. 驶向判断（向前延长）
        ref_lon = 2 * lon2 - lon1
        ref_lat = 2 * lat2 - lat1
        lon_ext, lat_ext = extend_point(lon2, lat2, ref_lon, ref_lat, extend_m)

        if intersect_rect(lon2, lat2, lon_ext, lat_ext, rect):
            j = i + 1
            while j < n - 1:
                next_pt = pts[j + 1]
                if curr.get('speed', 0) < 1 or next_pt.get('speed', 0) < 1:
                    break
                if rect.contains(next_pt['longitude'], next_pt['latitude']):
                    break
                curr_lon, curr_lat = curr['longitude'], curr['latitude']
                next_lon, next_lat = next_pt['longitude'], next_pt['latitude']
                ref_lon_next = 2 * next_lon - curr_lon
                ref_lat_next = 2 * next_lat - curr_lat
                le, lte = extend_point(next_lon, next_lat, ref_lon_next, ref_lat_next, extend_m)
                if not intersect_rect(next_lon, next_lat, le, lte, rect):
                    break
                j += 1
                curr = next_pt

            res.append({
                "trackId": track['trackId'],
                "areaId": areaId,
                "actionKey": code_map['approaching'],
                "startTime": pts[i]['timestamp'],
                "endTime": pts[j]['timestamp'],
                'properties': f'{pts[i]["longitude"]};{pts[i]["latitude"]}'
            })
            i = j + 1
            continue

        # 2b. 驶离判断（向后延长）
        ref_lon = 2 * lon1 - lon2
        ref_lat = 2 * lat1 - lat2
        lon_ext, lat_ext = extend_point(lon1, lat1, ref_lon, ref_lat, extend_m)

        if intersect_rect(lon1, lat1, lon_ext, lat_ext, rect):
            j = i + 1
            while j < n - 1:
                next_pt = pts[j + 1]
                if curr.get('speed', 0) < 1 or next_pt.get('speed', 0) < 1:
                    break
                if rect.contains(next_pt['longitude'], next_pt['latitude']):
                    break
                curr_lon, curr_lat = curr['longitude'], curr['latitude']
                next_lon, next_lat = next_pt['longitude'], next_pt['latitude']
                ref_lon_prev = 2 * curr_lon - next_lon
                ref_lat_prev = 2 * curr_lat - next_lat
                le, lte = extend_point(curr_lon, curr_lat, ref_lon_prev, ref_lat_prev, extend_m)
                if not intersect_rect(curr_lon, curr_lat, le, lte, rect):
                    break
                j += 1
                curr = next_pt

            res.append({
                "trackId": track['trackId'],
                "areaId": areaId,
                "actionKey": code_map['leaving'],
                "startTime": pts[i]['timestamp'],
                "endTime": pts[j]['timestamp'],
                'properties': f'{pts[i]["longitude"]};{pts[i]["latitude"]}'
            })
            i = j + 1
            continue

        i += 1

    return res


def check_impArea_port(data_input, all_area):
    for area in all_area:
        if area.get('checkDistance',None) is None:
            if area['area_type'] == 'port':
                area['checkDistance'] = portCheckDistance
            elif area['area_type'] == 'impArea':
                area['checkDistance'] = impAreaCheckDistance
    results = []
    for dataIn in data_input:
        for area in all_area:
            area_id = area['area_id']
            coords_bbox = area['coords_bbox']
            rect = Rect(coords_bbox)
            area_type = area['area_type']
            results += process_track(dataIn, rect, area_id, area_type)
            # print(json.dumps(results, ensure_ascii=False, indent=2))
    return results


if __name__ == '__main__':
    # 此处可自行调试
    pass

